#define _GNU_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <signal.h>
#include <netdb.h>
#include <fcntl.h>

#define ERR(source) (perror(source),\
		     fprintf(stderr,"%s:%d\n",__FILE__,__LINE__),\
		     exit(EXIT_FAILURE))

#define MSGCOUNT 10
#define GCOUNT 2
#define BACKLOG 4

int sethandler( void (*f)(int), int sigNo)
{
  struct sigaction act;
  memset(&act, 0, sizeof(struct sigaction));
  act.sa_handler = f;
  if (-1==sigaction(sigNo, &act, NULL))
    return -1;
  return 0;
}

ssize_t bulk_read(int fd, char *buf, size_t count)
{
  int c;
  size_t len=0;
  do
  {
    c=TEMP_FAILURE_RETRY(read(fd,buf,count));
    if(c<0) return c;
    if(0==c) return len;
    buf+=c;
    len+=c;
    count-=c;
  }
  while(count>0);
  return len;
}

ssize_t bulk_write(int fd, char *buf, size_t count)
{
  int c;
  size_t len=0;
  do
  {
    c=TEMP_FAILURE_RETRY(write(fd,buf,count));
    if(c<0) return c;
    buf+=c;
    len+=c;
    count-=c;
  }
  while(count>0);
  return len;
}

int make_socket(int domain, int type)
{
  int sock;
  sock = socket(domain,type,0);
  if(sock < 0) ERR("socket");
  return sock;
}

int bind_inet_socket(uint16_t port,int type)
{
  struct sockaddr_in addr;
  int socketfd,t=1;
  socketfd = make_socket(PF_INET,type);
  memset(&addr, 0, sizeof(struct sockaddr_in));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  if (setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&t, sizeof(t)))
    ERR("setsockopt");
  if(bind(socketfd,(struct sockaddr*) &addr,sizeof(addr)) < 0)
    ERR("bind");
  if(SOCK_STREAM==type)
    if(listen(socketfd, BACKLOG) < 0)
      ERR("listen");
  return socketfd;
}

void read_broadcast(int fds[GCOUNT],fd_set rfds) 
{
  int i,j,count;
  char buf[MSGCOUNT];
  for(i=0;i<GCOUNT;i++)
  {
    if(fds[i]!=-1 && FD_ISSET(fds[i],&rfds))
    {
      do
      {
        count=TEMP_FAILURE_RETRY(read(fds[i],buf,MSGCOUNT));
        if(count<0)
        {
          printf("DISCONNECT on READ\n");
          for(i=0;i<GCOUNT;i++)
            if(TEMP_FAILURE_RETRY(close(fds[i]))<0)
              ERR("close");
          exit(EXIT_FAILURE);
        }
        else
        {
          for(j=0;j<GCOUNT;j++)
          {
            if(j!=i&&bulk_write(fds[j],buf,count)<count)
            {
              printf("DISCONNECT on WRITE\n");
              for(i=0;i<GCOUNT;i++)
                if(TEMP_FAILURE_RETRY(close(fds[i]))<0)
                  ERR("close");
              exit(EXIT_FAILURE);
            }
          }
        }
      }
      while(count==MSGCOUNT);
    }
  }
}

void thread_work(int fds[GCOUNT])
{
  int con,fdmax=0;
  fd_set base_rfds, rfds;
  FD_ZERO(&base_rfds);
  for(con=0;con<GCOUNT;con++)
  {
    FD_SET(fds[con], &base_rfds);
    fdmax=(fds[con]>fdmax?fds[con]:fdmax);
  }
  for(;;)
  {
    rfds=base_rfds;
    if(TEMP_FAILURE_RETRY(select(fdmax+1,&rfds,NULL,NULL,NULL))<0)
      ERR("select");
    read_broadcast(fds,rfds);
  }
  for(;con>0;con--)
    if(fds[con]>0&&TEMP_FAILURE_RETRY(close(fds[con]))<0)
      ERR("close");
}

void add_new_clients(int sfd)
{
  int nfd[GCOUNT],i=0;
  for(;;)
  {
    if((nfd[i]=TEMP_FAILURE_RETRY(accept(sfd,NULL,NULL)))<0)
      ERR("accept");
    if(GCOUNT-1==i)
    {
      thread_work(nfd);
      for(;i>=0;i--)
        if(TEMP_FAILURE_RETRY(close(nfd[i]))<0)
          ERR("close");
    }
    else i++;
  }
  
  for(;i>=0;i--)
    if(TEMP_FAILURE_RETRY(close(nfd[i]))<0)
      ERR("close");
}

void usage(char *name)
{
  fprintf(stderr,"USAGE: %s port\n",name);
}

int main(int argc, char** argv)
{
  int socket;

  if(2!=argc)
  {
    usage(argv[0]);
    return EXIT_FAILURE;
  }

  socket=bind_inet_socket(atoi(argv[1]),SOCK_STREAM);
  if(sethandler(SIG_IGN,SIGPIPE)) ERR("Seting SIGPIPE:");

  add_new_clients(socket);
  
  if(TEMP_FAILURE_RETRY(close(socket))<0)ERR("close");
  return EXIT_SUCCESS;
}
